package cn.edu.hit.kg;

import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
//import net.sf.json.JSONObject;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;

/**
 * Created by Wang on 2018/7/16.
 */
public class ScriptContext implements IScriptContext {
    private static ScriptContext ourInstance = null;

    ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
    ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
    //    NashornScriptEngineFactory scriptEngineFactory = new NashornScriptEngineFactory();
//    ScriptEngine nashorn = scriptEngineFactory.getScriptEngine(ClassLoader.getSystemClassLoader());
    Invocable invocable = (Invocable) nashorn;

    public static ScriptContext getInstance() {
        if (ourInstance == null) {
            synchronized (ScriptContext.class) {
                if (ourInstance == null) {
                    ourInstance = new ScriptContext();
                }
            }
            ourInstance.init();
        }
        return ourInstance;
    }

    private ScriptContext() {
    }

    private void init() {
        loadJsUnit("javascript/core.js");   //core.js更改
        loadJsUnit("javascript/external.js");
        loadJavaExt();
    }

    private void loadJavaExt() {
        ServiceLoader<IMethod> methodLoader = ServiceLoader.load(IMethod.class);
        methodLoader.forEach(method -> {
            this.registService(method.name(),method.version(),method.asJSObject());
            this.registBinding(method.name(),method.name());
        });
    }

    public void loadJsUnit(String file) {
        try {
            InputStream inputStream = Neo4jConfigurationUtil.openFileStream(file);
            nashorn.eval(new BufferedReader(new InputStreamReader(inputStream)));
        } catch (ScriptException | IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void registService(String name, String version, JSObject serviceObject) {
        Map<String, JSObject> versionMap;
        if (serviceMap.containsKey(name)) {
            versionMap = serviceMap.get(name);
        } else {
            versionMap = new HashMap<>();
        }
        versionMap.put(version, serviceObject);
        Optional<String> maxVersion = versionMap.keySet().stream().reduce((s, s2) -> {
            if (s.equals(LASTEST)) return s2;
            if (s2.equals(LASTEST)) return s;
            return s.compareTo(s2) > 0 ? s : s2;
        });
        if (maxVersion.orElse(version).equals(version)) {
            versionMap.put(LASTEST, serviceObject);
        }

        serviceMap.put(name, versionMap);
    }

    @Override
    public void registBinding(String name, String callName) {
        bindings.put(callName, name);
    }

    @Override
    public JSObject getService(String name, String version) {
        Map<String, JSObject> versionMap = serviceMap.get(name);
        if (versionMap == null) {
            return null;
        }
        if (versionMap.containsKey(version)) {
            return versionMap.get(version);
        }
        return versionMap.get(LASTEST);
    }

    @Override
    public JSObject getService(String name) {
        return getService(name, LASTEST);
    }

    @Override
    public JSObject getServiceByBinding(String callName) {
        if (bindings.containsKey(callName)) {
            return getService(bindings.get(callName));
        }
        return null;
    }

    @Override
    public boolean containsBinding(String callName) {
        return bindings.containsKey(callName);
    }

    @Override
    public Object call(JSObject jsObject, Object... args){
        return jsObject.call(this,args);
    }

    private Map<String, String> bindings = new HashMap<>();
    private Map<String, Map<String, JSObject>> serviceMap = new HashMap<>();

    public static void main(String... args) {
//        Object fn = null;
//        try {
//            fn = nashorn.eval("(function(arr,params){return arr[0]-arr[1]>params[0];})");
//            nashorn.put("bgN",fn);

//            Object result = invocable.invokeFunction("bgN", new int[]{25,5},new int[]{10});
//            System.out.println(result);
//        } catch (URISyntaxException e) {
//            e.printStackTrace();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
        ScriptContext myContext = ScriptContext.getInstance();
        JSObject bgN = myContext.getServiceByBinding("bgN");
        if (bgN == null) {
            System.out.println("Cannot find bgN");
        } else {
            System.out.println(bgN.call(null, new int[]{25, 5}, new int[]{10}));
        }
    }

    @Override
    public Object getConstValue(String name) {
        // TODO 获取常量值
        throw new RuntimeException("常量值模块未实现！");
    }
}
